package net.aiion.weave.impl.xml;

import java.util.Collection;
import java.util.WeakHashMap;

import net.aiion.weave.impl.xml.events.AttributeXmlEventImpl;
import net.aiion.weave.impl.xml.events.CharactersXmlEventImpl;
import net.aiion.weave.impl.xml.events.CommentXmlEventImpl;
import net.aiion.weave.impl.xml.events.DocumentEndXmlEventImpl;
import net.aiion.weave.impl.xml.events.DocumentStartXmlEventImpl;
import net.aiion.weave.impl.xml.events.DocumentTypeDefinitionXmlEventImpl;
import net.aiion.weave.impl.xml.events.ElementEndXmlEventImpl;
import net.aiion.weave.impl.xml.events.ElementStartXmlEventImpl;
import net.aiion.weave.impl.xml.events.NamespaceXmlEventImpl;
import net.aiion.weave.impl.xml.events.ProcessingInstructionXmlEventImpl;
import net.aiion.weave.impl.xml.events.SourceXmlEventImpl;
import net.aiion.weave.spi.xml.XmlEventFactory;
import net.aiion.weave.spi.xml.XmlQualifiedName;
import net.aiion.weave.spi.xml.events.AttributeXmlEvent;
import net.aiion.weave.spi.xml.events.CharactersXmlEvent;
import net.aiion.weave.spi.xml.events.CommentXmlEvent;
import net.aiion.weave.spi.xml.events.DocumentEndXmlEvent;
import net.aiion.weave.spi.xml.events.DocumentStartXmlEvent;
import net.aiion.weave.spi.xml.events.DocumentTypeDefinitionXmlEvent;
import net.aiion.weave.spi.xml.events.ElementEndXmlEvent;
import net.aiion.weave.spi.xml.events.ElementStartXmlEvent;
import net.aiion.weave.spi.xml.events.NamespaceXmlEvent;
import net.aiion.weave.spi.xml.events.ProcessingInstructionXmlEvent;
import net.aiion.weave.spi.xml.events.SourceXmlEvent;
import net.aiion.weave.spi.xml.events.XmlEvent;

public class XmlEventFactoryImpl extends XmlEventFactory
{
    private static final int HASH_PRIME = 31;

    private final WeakHashMap<Integer, XmlEvent> cachedXmlEvents;

    public XmlEventFactoryImpl() {
        cachedXmlEvents = new WeakHashMap<>();
    }

    @Override
    public XmlQualifiedName createQualifiedName(final String aLocalName) {
        return new XmlQualifiedNameImpl(aLocalName);
    }

    @Override
    public XmlQualifiedName createQualifiedName(final String aNamespaceURI, final String aLocalName) {
        return new XmlQualifiedNameImpl(aNamespaceURI, aLocalName);
    }

    @Override
    public XmlQualifiedName createQualifiedName(final String aNamespaceURI, final String aLocalName,
        final String aPrefix)
    {
        return new XmlQualifiedNameImpl(aNamespaceURI, aLocalName, aPrefix);
    }

    @Override
    public SourceXmlEvent createSource(final String aSourceFileName) {
        return new SourceXmlEventImpl(aSourceFileName);
    }

    @Override
    public DocumentStartXmlEvent createDocumentStart() {
        return createDocumentStart("1.0");
    }

    @Override
    public DocumentStartXmlEvent createDocumentStart(final String aVersion) {
        return createDocumentStart(aVersion, "UTF-8");
    }

    @Override
    public DocumentStartXmlEvent createDocumentStart(final String aVersion, final String aEncoding) {
        return createDocumentStart(aVersion, aEncoding, false);
    }

    @Override
    public DocumentStartXmlEvent createDocumentStart(final String aVersion, final String aEncoding,
        final boolean aStandalone)
    {
        final int hashCode = hashCode(aVersion, aEncoding, aStandalone);
        if (cachedXmlEvents.containsKey(hashCode)) {
            return (DocumentStartXmlEvent) cachedXmlEvents.get(hashCode);
        }

        final DocumentStartXmlEvent documentStartXmlEvent = new DocumentStartXmlEventImpl(aVersion, aEncoding,
            aStandalone);
        cachedXmlEvents.put(hashCode, documentStartXmlEvent);
        return documentStartXmlEvent;
    }

    @Override
    public DocumentTypeDefinitionXmlEvent createDocumentTypeDefinition(final String aDocumentTypeDefinition) {
        final int hashCode = hashCode(aDocumentTypeDefinition);
        if (cachedXmlEvents.containsKey(hashCode)) {
            return (DocumentTypeDefinitionXmlEvent) cachedXmlEvents.get(hashCode);
        }

        final DocumentTypeDefinitionXmlEvent documentTypeDefinitionXmlEvent = new DocumentTypeDefinitionXmlEventImpl(
            aDocumentTypeDefinition);
        cachedXmlEvents.put(hashCode, documentTypeDefinitionXmlEvent);
        return documentTypeDefinitionXmlEvent;
    }

    @Override
    public CommentXmlEvent createComment(final String aComment) {
        return new CommentXmlEventImpl(aComment);
    }

    @Override
    public ProcessingInstructionXmlEvent createProcessingInstruction(final String aTarget, final String aData) {
        return new ProcessingInstructionXmlEventImpl(aTarget, aData);
    }

    @Override
    public ElementStartXmlEvent createElementStart(final String aPrefix, final String aNamespaceURI,
        final String aLocalName)
    {
        return createElementStart(new XmlQualifiedNameImpl(aNamespaceURI, aLocalName, aPrefix));
    }

    @Override
    public ElementStartXmlEvent createElementStart(final String aPrefix, final String aNamespaceURI,
        final String aLocalName, final Collection<AttributeXmlEvent> aXmlAttributes,
        final Collection<NamespaceXmlEvent> aXmlNamespaces)
    {
        return createElementStart(new XmlQualifiedNameImpl(aNamespaceURI, aLocalName, aPrefix), aXmlAttributes,
            aXmlNamespaces);
    }

    @Override
    public ElementStartXmlEvent createElementStart(final XmlQualifiedName aXmlQualifiedName) {
        return createElementStart(aXmlQualifiedName, null, null);
    }

    @Override
    public ElementStartXmlEvent createElementStart(final XmlQualifiedName aXmlQualifiedName,
        final Collection<AttributeXmlEvent> aXmlAttributes, final Collection<NamespaceXmlEvent> aXmlNamespaces)
    {
        return new ElementStartXmlEventImpl(aXmlQualifiedName, aXmlAttributes, aXmlNamespaces);
    }

    @Override
    public NamespaceXmlEvent createNamespace(final String aNamespaceURI) {
        return createNamespace("", aNamespaceURI);
    }

    @Override
    public NamespaceXmlEvent createNamespace(final String aPrefix, final String aNamespaceURI) {
        final int hashCode = hashCode(aPrefix, aNamespaceURI);
        if (cachedXmlEvents.containsKey(hashCode)) {
            return (NamespaceXmlEvent) cachedXmlEvents.get(hashCode);
        }

        final NamespaceXmlEvent namespaceXmlEvent = new NamespaceXmlEventImpl(aPrefix, aNamespaceURI);
        cachedXmlEvents.put(hashCode, namespaceXmlEvent);
        return namespaceXmlEvent;
    }

    @Override
    public AttributeXmlEvent createAttribute(final String aPrefix, final String aNamespaceURI, final String aLocalName,
        final String aValue)
    {
        return createAttribute(new XmlQualifiedNameImpl(aNamespaceURI, aLocalName, aPrefix), aValue);
    }

    @Override
    public AttributeXmlEvent createAttribute(final XmlQualifiedName anXmlQualifiedName, final String aValue) {
        return new AttributeXmlEventImpl(anXmlQualifiedName, aValue);
    }

    @Override
    public CharactersXmlEvent createCharacters(final String aCharacters) {
        return new CharactersXmlEventImpl(aCharacters);
    }

    @Override
    public CharactersXmlEvent createCharacters(final String aCharacters, final int aCharacterDataType) {
        return new CharactersXmlEventImpl(aCharacters, aCharacterDataType);
    }

    @Override
    public ElementEndXmlEvent
        createElementEnd(final String aPrefix, final String aNamespaceURI, final String aLocalName)
    {
        return createElementEnd(new XmlQualifiedNameImpl(aNamespaceURI, aLocalName, aPrefix));
    }

    @Override
    public ElementEndXmlEvent createElementEnd(final XmlQualifiedName anXmlQualifiedName) {
        return new ElementEndXmlEventImpl(anXmlQualifiedName);
    }

    @Override
    public DocumentEndXmlEvent createDocumentEnd() {
        return DocumentEndXmlEventImpl.getInstance();
    }

    private static int hashCode(final int aFirst, final int aSecond) {
        return HASH_PRIME * (HASH_PRIME + aFirst) + aSecond;
    }

    private static int hashCode(final int aFirst, final int aSecond, final int aThird) {
        return HASH_PRIME * hashCode(aFirst, aSecond) + aThird;
    }

    private static int hashCode(final String aFirst) {
        return aFirst.hashCode();
    }

    private static int hashCode(final String aFirst, final String aSecond) {
        return hashCode(aFirst.hashCode(), aSecond.hashCode());
    }

    private static int hashCode(final String aFirst, final String aSecond, final boolean aThird) {
        return hashCode(aFirst.hashCode(), aSecond.hashCode(), Boolean.valueOf(aThird).hashCode());
    }
}
